﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WebSocket4Net;
using WinCustomsService.Model;

namespace WinCustomsService
{
    /// <summary>
    /// 加签帮助 https://blog.csdn.net/q1606671375/article/details/121859246
    /// </summary>
    public class Sign311Helper
    {
       
        private const int REQUEST_TIME_WAIT = 200;
        private const int TIME_OUT = 60000;
        private const string URL = "ws://127.0.0.1:61232";
        private static int m_lastRequestTime;
        private static readonly object m_lockThis = new object();



        public static State WebSocketSign(string jsonParamStr,string passwd,long id,int typeId = 0)
        {
            lock(m_lockThis)
            {
                if(Environment.TickCount - m_lastRequestTime < REQUEST_TIME_WAIT)
                    Thread.Sleep(REQUEST_TIME_WAIT);

                m_lastRequestTime = Environment.TickCount;
            }

            WebSocket websocket = new WebSocket(URL);
            State state = new State()
            {
                Parameter = jsonParamStr,
                Passwd =passwd,
                Id = id
            };

            websocket.Error += new EventHandler<SuperSocket.ClientEngine.ErrorEventArgs>((sender, e) =>
            {
                state.NoWait = true;
                state.Code = "404";
                state.Message = "Error:" + e.Exception.Message;
                state.Data = string.Empty;
            });

            websocket.MessageReceived += new EventHandler<MessageReceivedEventArgs>((sender, e) =>
            {
                try
                {
                    state.Message = "Success";
                    state.Data = e.Message;
                    if(typeId == 0)
                        Websocket_MessageReceived(websocket, state);
                    else
                        Websocket_MessageReceivedPEM(websocket, state);
                }
                catch(Exception ex)
                {
                    websocket.Close();
                    websocket.Dispose();

                    state.NoWait = true;
                    state.Code = "404";
                    state.Message = "Error:" + ex.Message;
                    state.Data = string.Empty;
                }
            });

            websocket.Open();

            int time = Environment.TickCount;

            while(!state.NoWait && Environment.TickCount - time < TIME_OUT)
                Thread.Sleep(1);

            websocket.Close();
            websocket.Dispose();
            state.Code = "200";

            if(Environment.TickCount - time > TIME_OUT)
                state.Message = "request time out.";

            return state;
        }

        private static void Websocket_MessageReceived(WebSocket webSocket, State state)
        {
            if(state.Data.Contains("握手成功"))
            {
                StringBuilder postJson = new StringBuilder();

                postJson.Append(state.Parameter);

                JObject jObject = new JObject
                {
                    ["_method"] = "cus-sec_SpcSignDataAsPEM",
                    ["_id"] = state.Id+1
                };

                JObject args = new JObject
                {
                    ["inData"] = postJson.ToString(),
                    ["passwd"] = state.Passwd
                };
              //  LogHelper.WriteLog($"state:" + JsonConvert.SerializeObject(state));
             //   LogHelper.WriteLog($"args:" + JsonConvert.SerializeObject(args));
                jObject["args"] = args;

                webSocket.Send(jObject.ToString());
            }
            else
            {
                state.Data = JObject.Parse(state.Data)["_args"].ToString();
                state.NoWait = true;
            }
        }


        private static void Websocket_MessageReceivedPEM(WebSocket webSocket, State state)
        {
            if(state.Data.Contains("握手成功"))
            {
                StringBuilder postJson = new StringBuilder();

                postJson.Append(state.Parameter);

                JObject jObject = new JObject
                {
                    ["_method"] = "cus-sec_SpcGetSignCertAsPEM",
                    ["_id"] = 1
                };

                JObject args = new JObject
                {
                   // ["inData"] = postJson.ToString(),
                   // ["passwd"] = state.Passwd
                };
                // LogHelper.WriteLog($"state:" + JsonConvert.SerializeObject(state));
                 //  LogHelper.WriteLog($"args:" + JsonConvert.SerializeObject(args));
                jObject["args"] = args;

                webSocket.Send(jObject.ToString());
            }
            else
            {
                state.Data = JObject.Parse(state.Data)["_args"].ToString();
                state.NoWait = true;
            }
        }

        public static string GetSignature(CustomsClearanceEditor customs)
        {

            try
            {
                State state2 = WebSocketSign(string.Empty, customs.Passwd, customs.Id, 1);
                LogHelper.WriteLog($"state2:" + JsonConvert.SerializeObject(state2));
                JObject jObject2 = JObject.Parse(state2.Data);
                JArray jArray2 = jObject2["Data"].ToObject<JArray>();
                string x509Data = jArray2[0].ToString();


                string canonicalizeXmlData = Canonicalize(customs.XmlStr);
              
                SHA1 sha = new SHA1CryptoServiceProvider();
                string digest = Convert.ToBase64String(sha.ComputeHash(Encoding.UTF8.GetBytes(canonicalizeXmlData)));
              
                string signedInfoContent = GetSignedInfoContent(digest);
        
                string signedInfoContentPlus = Canonicalize(GetSignedInfoContentPlus(digest));


                State state = WebSocketSign(signedInfoContentPlus, customs.Passwd, customs.Id,0);
                customs.Error = state.Message;
                LogHelper.WriteLog($"state:" + JsonConvert.SerializeObject(state));
                JObject jObject = JObject.Parse(state.Data);
                JArray jArray = jObject["Data"].ToObject<JArray>();
                string keyName =  jArray[1].ToString();
                string signatureValue =  jArray[0].ToString();
                string signatureXml = ComposeW3cEnvelopedSignedInfoXml(signedInfoContent, signatureValue, keyName, x509Data);//Convert.ToBase64String(Encoding.UTF8.GetBytes(canonicalizeXmlData))


                return ComposeW3cEnvelopedXmlEx(signatureXml, canonicalizeXmlData);
            }
            catch(Exception ex)
            {
               // LogHelper.WriteLog($"ex:" + JsonConvert.SerializeObject(ex));
                customs.Error = JsonConvert.SerializeObject(ex);
                return string.Empty;
            }
           
        }

        private static string GetSignedInfoContentPlus(string digest)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("<ds:SignedInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"").Append(" xmlns:ceb=\"http://www.chinaport.gov.cn/ceb\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"").Append(">");
            stringBuilder.Append("<ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/>");
            stringBuilder.Append("<ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>");
            stringBuilder.Append("<ds:Reference URI=\"\">");
            stringBuilder.Append("<ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></ds:Transforms>");
            stringBuilder.Append("<ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>");
            stringBuilder.Append("<ds:DigestValue>");
            stringBuilder.Append(digest);
            stringBuilder.Append("</ds:DigestValue></ds:Reference></ds:SignedInfo>");
            return stringBuilder.ToString();
        }

        private static string GetSignedInfoContent(string digest)
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("<ds:SignedInfo>");
            stringBuilder.Append("<ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"/>");
            stringBuilder.Append("<ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>");
            stringBuilder.Append("<ds:Reference URI=\"\">");
            stringBuilder.Append("<ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></ds:Transforms>");
            stringBuilder.Append("<ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/>");
            stringBuilder.Append("<ds:DigestValue>");
            stringBuilder.Append(digest);
            stringBuilder.Append("</ds:DigestValue></ds:Reference></ds:SignedInfo>");
            return stringBuilder.ToString();
        }

        public static string Canonicalize(string xmlData)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.PreserveWhitespace = true;
            xmlDocument.LoadXml(xmlData);

            using(MemoryStream memoryStream = new MemoryStream())
            {
                using(XmlWriter xmlWriter = XmlWriter.Create(memoryStream))
                {
                    xmlDocument.WriteTo(xmlWriter);
                    xmlWriter.Flush();
                }

                memoryStream.Position = 0L;
                XmlDsigC14NTransform xmlDsigC14NTransform = new XmlDsigC14NTransform();
                xmlDsigC14NTransform.LoadInput(memoryStream);

                using(MemoryStream transformMemoryStream = (MemoryStream)xmlDsigC14NTransform.GetOutput(typeof(Stream)))
                {
                    return Encoding.UTF8.GetString(transformMemoryStream.ToArray());
                }
            }
        }

        private static string ComposeW3cEnvelopedSignedInfoXml(string signedInfo, string signatureValue, string keyName, string x509Data)
        {
           
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">");
            stringBuilder.Append(signedInfo);
            stringBuilder.Append("<ds:SignatureValue>");
            stringBuilder.Append(signatureValue);
            stringBuilder.Append("</ds:SignatureValue>");
            stringBuilder.Append("<ds:KeyInfo><ds:KeyName>");
            stringBuilder.Append(keyName);
            stringBuilder.Append("</ds:KeyName>");
            stringBuilder.Append("<ds:X509Data>");
            stringBuilder.Append("<ds:X509Certificate>");
            stringBuilder.Append(x509Data);
            stringBuilder.Append("</ds:X509Certificate>");
            stringBuilder.Append("</ds:X509Data>");
            stringBuilder.Append("</ds:KeyInfo>");
            stringBuilder.Append("</ds:Signature>");
            return stringBuilder.ToString();
        }

        private static string ComposeW3cEnvelopedXmlEx(string signatureXml, string srcData)
        {
            int num = srcData.LastIndexOf("</");
            return string.Format("<?xml version=\"1.0\" encoding=\"UTF-8\"?>{0}{1}{2}", srcData.Substring(0, num), signatureXml, srcData.Substring(num));
        }
    }

    public class JsonParam
    {
        public string JsonParamStr { get; set; }
    }

    public class State
    {
        public string Message { get; set; }

        public bool NoWait { get; set; }

        public string Data { get; set; }

        public string Parameter { get; set; }

        public string Code { get; set; }

        public   string Passwd { get; set; }
        public long Id { get; set; }
    }

}
